/**
 * The MIT License
 * Copyright (c) 2010 Tad Glines
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package net.towee.server.sio.server.transport;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.towee.server.authentication.impl.SessionHttpManagerImpl;
import net.towee.server.sio.common.ConnectionState;
import net.towee.server.sio.common.DisconnectReason;
import net.towee.server.sio.common.SocketIOException;
import net.towee.server.sio.server.SocketIOClosedException;
import net.towee.server.sio.server.SocketIOFrame;
import net.towee.server.sio.server.SocketIOInbound;
import net.towee.server.sio.server.SocketIOSession;
import net.towee.server.sio.server.Transport;
import net.towee.server.sio.server.SocketIOFrame.FrameType;

import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class WebSocketTransport extends AbstractTransport {
	private static final Logger LOG = LoggerFactory.getLogger(WebSocketTransport.class);
	
	public static final String TRANSPORT_NAME = "websocket";
	private final WebSocketFactory wsFactory;
	
	private class SessionWrapper implements WebSocket, WebSocket.OnTextMessage, SocketIOSession.SessionTransportHandler {
		private final SocketIOSession session;
		private final HttpSession httpSession;
		private Connection outbound = null;

		SessionWrapper(SocketIOSession session, HttpSession httpSession) {
			this.session = session;
			this.httpSession = httpSession;
		}
		
		@Override
		public void onOpen(final Connection outbound) {
			this.outbound = outbound;
			
			// Send connect message
			try {
				sendMessage(new SocketIOFrame(FrameType.CONNECT, null, null, null, null, null, null));
				sendMessage(new SocketIOFrame(FrameType.HEARTBEAT, null, null, null, null, null, null));
				session.onConnect(this);
				LOG.info("WSocket client connected");
			} catch (SocketIOException e) {
				LOG.error("[WS] Can't send CONNECT message to the client");
			}
		}

		@Override
		public void onClose(int closeCode, String message) {
			session.onShutdown();
		}
		
		@Override
		public void onMessage(String message) {
			LOG.info("onMessage: "+message);
			
			// DeIdle session
			SessionHttpManagerImpl.INSTANCE.access(httpSession, false);
			
			// Restart HB
			session.setHeartbeatTimeout();
			
			// Parse messages
			SocketIOFrame messages = SocketIOFrame.decodePacket(message);
			
			// Run session handler
			session.onFrame(messages);
		}

		@Override
		public void disconnect() {
			session.onDisconnect(DisconnectReason.DISCONNECT);
			outbound.disconnect();
		}

		@Override
		public void close() {
			session.startClose();
		}

		@Override
		public ConnectionState getConnectionState() {
			return session.getConnectionState();
		}

		@Override
		public void sendMessage(SocketIOFrame frame) throws SocketIOException {
			//if (outbound.isOpen()) {
				try {
					LOG.info("Send message: "+frame.encode());
					outbound.sendMessage(frame.encode());
				} catch (IOException e) {
					outbound.disconnect();
					throw new SocketIOException(e);
				}
			//} else {
				//throw new SocketIOClosedException();
			//}
		}

		@Override
		public void handle(HttpServletRequest request,
				HttpServletResponse response, SocketIOSession session) throws IOException {
    		response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unexpected request on upgraded WebSocket connection");
    		return;
		}

		@Override
		public void disconnectWhenEmpty() {
		}

		@Override
		public void abort() {
			outbound.disconnect();
			outbound = null;
			session.onShutdown();
		}

		@Override
		public void sendMessage(String message) throws SocketIOException {
			sendMessage(new SocketIOFrame(FrameType.MESSAGE, message, false, null, null, null, null));
		}
	}

	public WebSocketTransport(int bufferSize, int maxIdleTime) {
		wsFactory = new WebSocketFactory(null);
		wsFactory.setBufferSize(bufferSize);
		wsFactory.setMaxIdleTime(maxIdleTime);
	}
	
	@Override
	public String getName() {
		return TRANSPORT_NAME;
	}
	
	@Override
	public void handle(HttpServletRequest request,
			HttpServletResponse response,
			Transport.InboundFactory inboundFactory,
			SocketIOSession.Factory sessionFactory)
			throws IOException {

		// Get session(it was created with handshake)
		String sessionId = extractSessionId(request);
		SocketIOSession session = sessionFactory.getSession(sessionId);
		
		// Check session existing
		if(session == null) {
			response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "No handshaked session");
			return;
		}
		
		// Create socket otherwise
		else if ("GET".equals(request.getMethod()) && "WebSocket".equalsIgnoreCase(request.getHeader("Upgrade"))) {
			boolean hixie = request.getHeader("Sec-WebSocket-Key1") != null;
            
            String protocol=request.getHeader(hixie ? "Sec-WebSocket-Protocol" : "WebSocket-Protocol");
            if (protocol == null)
                protocol=request.getHeader("Sec-WebSocket-Protocol");

	        String host=request.getHeader("Host");
	        String origin=request.getHeader("Origin");
	        if (origin == null) {
	        	origin = host;
	        }
	
	        SocketIOInbound inbound = inboundFactory.getInbound(request,
	        		protocol == null ? null : protocol.split(" "));
	        if (inbound == null) {
	        	if (hixie) {
                    response.setHeader("Connection","close");
	        	}
                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
	        } else {
	        	session.setSocketInbound( inbound ); 
		        SessionWrapper wrapper = new SessionWrapper(session, request.getSession());
		        wsFactory.upgrade(request,response,wrapper,protocol);
	        }
		} else {
    		response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid " + TRANSPORT_NAME + " transport request");
		}
	}
}
